package net.qiqbframework.spring.accessing;

import net.qiqbframework.accessing.ClientApi;
import net.qiqbframework.accessing.ClientChannel;
import net.qiqbframework.accessing.SimpleClientChannel;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.type.filter.AnnotationTypeFilter;

import java.util.*;

public class AggregateClientApiBeanPostProcessor implements BeanDefinitionRegistryPostProcessor, ApplicationContextAware, SmartInitializingSingleton {

    private ApplicationContext applicationContext;

    private BeanDefinitionRegistry beanDefinitionRegistry;

    private ClientChannel clientChannel = new SimpleClientChannel();

    public static Set<String> getBasePackages(ApplicationContext applicationContext) {
        Set<String> basePackages = new HashSet<>();

        // 获取主类（启动类）
        String[] beanNames = applicationContext.getBeanDefinitionNames();
        for (String beanName : beanNames) {
            Class<?> beanType = applicationContext.getType(beanName);
            if (beanType != null) {
                final Set<ComponentScan> mergedAnnotation = AnnotatedElementUtils.findAllMergedAnnotations(beanType, ComponentScan.class);
                ComponentScan componentScan = AnnotationUtils.findAnnotation(beanType, ComponentScan.class);
                if (componentScan != null) {
                    final String[] strings = componentScan.basePackages();
                    basePackages.addAll(Arrays.asList(componentScan.basePackages()));
                    if (basePackages.isEmpty()) {
                        basePackages.add(beanType.getPackage().getName());
                    }
                }
            }
        }

        return basePackages;
    }

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        this.beanDefinitionRegistry = registry;
        Set<String> basePackages = new HashSet<>();//getBasePackages(applicationContext);//  applicationContext.getEnvironment().getProperty("spring.component.scan.base-packages", String[].class, new String[]{});
        basePackages.add("net.qiqb.project.nav.client.apis");


        ClassPathScanningCandidateComponentProvider scanner = new MyClassPathScanningCandidateComponentProvider();
        scanner.addIncludeFilter(new AnnotationTypeFilter(ClientApi.class));

        for (String basePackage : basePackages) {
            Set<BeanDefinition> candidates = scanner.findCandidateComponents(basePackage);
            for (BeanDefinition bd : candidates) {
                try {
                    Class clazz = Class.forName(bd.getBeanClassName());
                    final AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition(AggregateClientApiFactoryBean.class).getBeanDefinition();
                    // final ConstructorArgumentValues constructorArgumentValues = beanDefinition.getConstructorArgumentValues();
                    // constructorArgumentValues.addIndexedArgumentValue(0, clazz);

                    beanDefinition.setPrimary(true);
                    beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(clazz);
                    // beanDefinition.getPropertyValues().add("interfaceClass", clientClass);
                    // beanDefinition.getPropertyValues().add("blockClientManager", blockClientManager);

                    // beanDefinition.setAttribute("factoryBeanObjectType", clientClass.getName());
                    registry.registerBeanDefinition(clazz.getName(), beanDefinition);

                } catch (Exception e) {
                    throw new RuntimeException("Failed to create proxy for " + bd.getBeanClassName(), e);
                }
            }
        }
    }

    private ConfigurableListableBeanFactory beanFactory;

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        // No post processing required for bean factory
        this.beanFactory = beanFactory;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    @Override
    public void afterSingletonsInstantiated() {

        //
        final Map<String, Object> clientApiMaps = beanFactory.getBeansWithAnnotation(ClientApi.class);
        Map<String, AggregateClientApiFactoryBean<?>> aggregateClientApiFactoryBeanMap = new HashMap<>();
        for (Map.Entry<String, Object> entry : clientApiMaps.entrySet()) {
            final String beanName = entry.getKey();

            try {
                final AggregateClientApiFactoryBean beanFactoryBean = (AggregateClientApiFactoryBean) beanFactory.getBean("&" + beanName);
                //
                aggregateClientApiFactoryBeanMap.put(beanName, beanFactoryBean);
            } catch (BeansException e) {
                continue;
            }
        }
        for (Map.Entry<String, Object> entry : clientApiMaps.entrySet()) {
            final String beanName = entry.getKey();
            if (aggregateClientApiFactoryBeanMap.containsKey(beanName)) {
                continue;
            }
            // 找到
            final Object bean = entry.getValue();
            for (Class<?> anInterface : bean.getClass().getInterfaces()) {
                final AggregateClientApiFactoryBean aggregateClientApiFactoryBean = aggregateClientApiFactoryBeanMap.get(anInterface.getName());
                if (aggregateClientApiFactoryBean != null) {
                    aggregateClientApiFactoryBean.setTarget(bean);
                }
            }

        }
        // 验证target 是否存在
        for (Map.Entry<String, AggregateClientApiFactoryBean<?>> entry : aggregateClientApiFactoryBeanMap.entrySet()) {
            entry.getValue().verifyTarget();
        }
    }
}